package cn.donting.jme.ohos.system;

import cn.donting.jme.ohos.input.DummyJoyInput;
import cn.donting.jme.ohos.input.DummyTouchInput;
import cn.donting.jme.ohos.renderer.GLSurfaceProvider;
import cn.donting.jme.ohos.renderer.OhosGL;
import cn.donting.jme.ohos.util.OhosBufferAllocator;
import com.jme3.input.*;
import com.jme3.input.controls.SoftTextDialogInputListener;
import com.jme3.input.dummy.DummyInput;
import com.jme3.input.dummy.DummyKeyInput;
import com.jme3.input.dummy.DummyMouseInput;
import com.jme3.opencl.Context;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.opengl.*;
import com.jme3.system.*;
import com.jme3.util.BufferAllocatorFactory;
import ohos.agp.components.surfaceprovider.SurfaceProvider;
import ohos.agp.graphics.SurfaceOps;
import ohos.agp.render.opengl.EGLConfig;
import ohos.agp.render.opengl.GLES1X;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

public class OGLESContext implements JmeContext, SoftTextDialogInput, SurfaceOps.Callback, GLSurfaceProvider.Renderer {
    private static final Logger logger = Logger.getLogger(OGLESContext.class.getName());
    protected final AtomicBoolean created = new AtomicBoolean(false);
    protected final AtomicBoolean renderable = new AtomicBoolean(false);
    protected final AtomicBoolean needClose = new AtomicBoolean(false);
    protected AppSettings settings = new AppSettings(true);

    protected GLRenderer renderer;
    protected Timer timer;
    protected SystemListener listener;
    protected boolean autoFlush = true;
    protected long minFrameDuration = 0;                   // No FPS cap
    protected long lastUpdateTime = 0;

    static {
        final String implementation = BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION;

        if (System.getProperty(implementation) == null) {
            System.setProperty(implementation, OhosBufferAllocator.class.getName());
        }
    }


    public SurfaceProvider creatSurfaceProvider(ohos.app.Context context) {
        SurfaceProvider surfaceProvider = new SurfaceProvider(context);
        SurfaceOps surfaceOps = surfaceProvider.getSurfaceOps().get();
        surfaceOps.addCallback(this);

        return surfaceProvider;
    }

    public SurfaceProvider creatGLSurfaceProvider(ohos.app.Context context) {
        GLSurfaceProvider surfaceProvider = new GLSurfaceProvider(context);
        surfaceProvider.setWidth(900);
        surfaceProvider.setHeight(600);
//        SurfaceOps surfaceOps = surfaceProvider.getSurfaceOps().get();
//        surfaceOps.addCallback(this);
        surfaceProvider.setEGLConfigChooser(new OhosConfigChooser(settings));
        surfaceProvider.setRenderer(this);

        return surfaceProvider;
    }

    @Override
    public void requestDialog(int id, String title, String initialValue, SoftTextDialogInputListener listener) {
        logger.log(Level.ALL, "requestDialog");
    }

    @Override
    public Type getType() {
        return Type.Display;
    }

    @Override
    public void setSettings(AppSettings settings) {
        this.settings.copyFrom(settings);
//        if (androidInput != null) {
//            androidInput.loadSettings(settings);
//        }

        if (settings.getFrameRate() > 0) {
            minFrameDuration = (long) (1000d / settings.getFrameRate()); // ms
            logger.log(Level.FINE, "Setting min tpf: {0}ms", minFrameDuration);
        } else {
            minFrameDuration = 0;
        }
    }

    @Override
    public void setSystemListener(SystemListener listener) {
        this.listener = listener;
    }

    @Override
    public AppSettings getSettings() {
        return settings;
    }

    @Override
    public Renderer getRenderer() {
        return renderer;
    }

    @Override
    public Context getOpenCLContext() {
        logger.warning("OpenCL is not yet supported on android");
        return null;
    }

    @Override
    public MouseInput getMouseInput() {
        return new DummyMouseInput();
    }

    @Override
    public KeyInput getKeyInput() {
        return new DummyKeyInput();
    }

    @Override
    public JoyInput getJoyInput() {
        return new DummyJoyInput();
    }

    @Override
    public TouchInput getTouchInput() {
        return new DummyTouchInput();
    }

    @Override
    public Timer getTimer() {
        return timer;
    }

    @Override
    public void setTitle(String title) {
    }

    @Override
    public boolean isCreated() {
        return created.get();
    }

    @Override
    public boolean isRenderable() {
        return renderable.get();
    }

    @Override
    public void setAutoFlushFrames(boolean enabled) {
        this.autoFlush = enabled;
    }

    /**
     * De-initialize in the OpenGL thread.
     */
    protected void deinitInThread() {
        if (renderable.get()) {
            created.set(false);
            if (renderer != null) {
                renderer.cleanup();
            }

            listener.destroy();

            listener = null;
            renderer = null;
            timer = null;

            // do android specific cleaning here
            logger.fine("Display destroyed.");

            renderable.set(false);
        }
    }

    @Override
    public void create(boolean waitFor) {
        if (needClose.get()) {
            deinitInThread();
            return;
        }

        if (!renderable.get()) {
            if (created.get()) {
                logger.fine("GL Surface is setup, initializing application");
                listener.initialize();
                renderable.set(true);
            }
        } else {
            if (!created.get()) {
                throw new IllegalStateException("onDrawFrame without create");
            }

            listener.update();
            if (autoFlush) {
                renderer.postFrame();
            }

            long updateDelta = System.currentTimeMillis() - lastUpdateTime;

            // Enforce a FPS cap
            if (updateDelta < minFrameDuration) {
//                    logger.log(Level.INFO, "lastUpdateTime: {0}, updateDelta: {1}, minTimePerFrame: {2}",
//                            new Object[]{lastUpdateTime, updateDelta, minTimePerFrame});
                try {
                    Thread.sleep(minFrameDuration - updateDelta);
                } catch (InterruptedException e) {
                }
            }
            lastUpdateTime = System.currentTimeMillis();
        }
    }

    @Override
    public void restart() {

    }

    public void destroy() {
        destroy(true);
    }

    protected void waitFor(boolean createdVal) {
        while (renderable.get() != createdVal) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException ex) {
            }
        }
    }

    @Override
    public void destroy(boolean waitFor) {
        needClose.set(true);
        if (waitFor) {
            waitFor(false);
        }
    }


    protected void initInThread() {
        created.set(true);

        logger.fine("OGLESContext create");
        logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName());

        // Setup unhandled Exception Handler
        Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread thread, Throwable thrown) {
                listener.handleError("Exception thrown in " + thread.toString(), thrown);
            }
        });

        timer = new NanoTimer();
        GL gl = new OhosGL();
        if (settings.getBoolean("GraphicsDebug")) {
            gl = (GL) GLDebug.createProxy(gl, gl, GL.class, GL2.class, GLES_30.class, GLFbo.class, GLExt.class);
        }
        if (settings.getBoolean("GraphicsTrace")) {
            gl = (GL) GLTracer.createGlesTracer(gl, GL.class, GLES_30.class, GLFbo.class, GLExt.class);
        }
        renderer = new GLRenderer(gl, (GLExt) gl, (GLFbo) gl);
        renderer.initialize();

        JmeSystem.setSoftTextDialogInput(this);

        needClose.set(false);
    }


    @Override
    public void surfaceCreated(SurfaceOps surfaceOps) {
        if (created.get() && renderer != null) {
            renderer.resetGLObjects();
        } else {
            if (!created.get()) {
                logger.fine("GL Surface created, initializing JME3 renderer");
                initInThread();
            } else {
                logger.warning("GL Surface already created");
            }
        }
    }

    @Override
    public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {

    }

    @Override
    public void surfaceDestroyed(SurfaceOps surfaceOps) {

    }

    @Override
    public void onSurfaceCreated(GLES1X gl, EGLConfig config) {
        if (created.get() && renderer != null) {
            renderer.resetGLObjects();
        } else {
            if (!created.get()) {
                logger.fine("GL Surface created, initializing JME3 renderer");
                initInThread();
            } else {
                logger.warning("GL Surface already created");
            }
        }
    }

    @Override
    public void onSurfaceChanged(GLES1X gl, int width, int height) {
        logger.log(Level.FINE, "GL Surface changed, width: {0} height: {1}", new Object[]{width, height});
        // update the application settings with the new resolution
        settings.setResolution(width, height);
        // reload settings in androidInput so the correct touch event scaling can be
        // calculated in case the surface resolution is different than the view
//        androidInput.loadSettings(settings);
        // if the application has already been initialized (ie renderable is set)
        // then call reshape so the app can adjust to the new resolution.
        if (renderable.get()) {
            logger.log(Level.FINE, "App already initialized, calling reshape");
            listener.reshape(width, height);
        }
    }

    @Override
    public void onDrawFrame(GLES1X gl) {
        if (needClose.get()) {
            deinitInThread();
            return;
        }

        if (!renderable.get()) {
            if (created.get()) {
                logger.fine("GL Surface is setup, initializing application");
                listener.initialize();
                renderable.set(true);
            }
        } else {
            if (!created.get()) {
                throw new IllegalStateException("onDrawFrame without create");
            }

            listener.update();
            if (autoFlush) {
                renderer.postFrame();
            }

            long updateDelta = System.currentTimeMillis() - lastUpdateTime;

            // Enforce a FPS cap
            if (updateDelta < minFrameDuration) {
//                    logger.log(Level.INFO, "lastUpdateTime: {0}, updateDelta: {1}, minTimePerFrame: {2}",
//                            new Object[]{lastUpdateTime, updateDelta, minTimePerFrame});
                try {
                    Thread.sleep(minFrameDuration - updateDelta);
                } catch (InterruptedException e) {
                }
            }

            lastUpdateTime = System.currentTimeMillis();

        }
    }
}
